package com.haoqizhe.kernel.jwt.utils;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.haoqizhe.kernel.jwt.properties.JwtProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * jwt 操作类
 *
 * @author haoqizhe.li
 * @date 2018/10/16
 **/
@Slf4j
@Configuration
@EnableConfigurationProperties({JwtProperties.class})
public class JwtTokenUtil {

    @Autowired
    private JwtProperties jwtProperties;

    /**
     * 记住我key
     */
    public static final String REMEMBER_ME = "REMEMBER_ME";

    /**
     * 创建token
     *
     * @param isRememberMe 是否记住我
     * @return token
     */
    public String createToken(String username, Map<String, Object> map, boolean isRememberMe) {
        checkSignType();

        if (map == null) {
            map = new HashMap<>(0);
        }
        map.put(REMEMBER_ME, isRememberMe);

        long expiration = isRememberMe ? jwtProperties.getRememberExpire() : jwtProperties.getExpire();
        return Jwts.builder()
                .signWith(jwtProperties.getSignType(), jwtProperties.getPublicKey())
                .setClaims(map)
                .setIssuer(jwtProperties.getIss())
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
                .setId(UUID.randomUUID().toString().replaceAll("-", ""))
                .compact();
    }

    public Long expire() {
        return jwtProperties.getExpire();
    }

    public Long rememberExpire() {
        return jwtProperties.getRememberExpire();
    }

    public Long expireSecond() {
        return jwtProperties.getRefreshTime();
    }

    /**
     * 从token中获取用户名
     */
    public String getUsername(String token) {
        return getClaimsFromToken(token).getSubject();
    }


    public boolean getRememberMe(String token) {
        return (Boolean) getClaimsFromToken(token).get(REMEMBER_ME);
    }


    /**
     * 是否已过期
     */
    public boolean isExpiration(String token) {
        Claims claims = getClaimsFromToken(token);
        if (claims == null) {
            return true;
        }
        return claims.getExpiration().before(new Date());
    }

    /**
     * 是否需要刷新token
     *
     * @param token 旧token
     * @return
     */
    public boolean isRefreshToken(String token) {

        Long refreshTime = jwtProperties.getRefreshTime();
        if (ObjectUtil.isNull(refreshTime) || refreshTime == 0) {
            return false;
        }

        Date expir = getClaimsFromToken(token).getExpiration();
        long time = DateUtil.betweenMs(expir, new Date());
        return time <= jwtProperties.getRefreshTime() * 1000;
    }

    /**
     * 创建时间
     *
     * @param token
     * @return
     */
    public Date getCreateDate(String token) {
        return getClaimsFromToken(token).getIssuedAt();
    }

    /**
     * 失效时间
     *
     * @param token
     * @return
     */
    public Date getExpiration(String token) {
        return getClaimsFromToken(token).getExpiration();
    }

    /**
     * 刷新token
     *
     * @param token
     * @return
     */
    public String refreshToken(String token) {
        String refreshedToken = null;
        try {
            Claims claims = getClaimsFromToken(token);
            refreshedToken = generateToken(claims);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

    /**
     * 从数据声明生成令牌
     *
     * @param claims 数据声明
     * @return 令牌
     */
    public String generateToken(Map<String, Object> claims) {
        checkSignType();
        Boolean isRememberMe = Boolean.parseBoolean(String.valueOf(claims.get(REMEMBER_ME)));
        long expiration = isRememberMe ? jwtProperties.getRememberExpire() : jwtProperties.getExpire();
        Date expirationDate = new Date(System.currentTimeMillis() + expiration * 1000);
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(expirationDate)
                .signWith(jwtProperties.getSignType(), jwtProperties.getPublicKey())
                .compact();
    }

    /**
     * token验签 解析
     *
     * @param token jwt token
     * @return
     */
    public Claims getClaimsFromToken(String token) {
        checkSignType();
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(jwtProperties.getPrivateKey())
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    protected void checkSignType() {

        if (jwtProperties.getSignType().isHmac()) {
            if (StrUtil.isBlank(jwtProperties.getPublicKey())) {
                throw new SignatureException("sign hmac public key must not be empty");
            }
            jwtProperties.setPrivateKey(jwtProperties.getPublicKey());
        }

        if (jwtProperties.getSignType().isRsa() || jwtProperties.getSignType().isEllipticCurve()) {
            if (StrUtil.isBlank(jwtProperties.getPublicKey()) || StrUtil.isBlank(jwtProperties.getPrivateKey())) {
                throw new SignatureException("sign rsa/es public key and private key must not be empty");
            }
        }

        switch (jwtProperties.getSignType()) {
            case ES256:
                break;
            case ES384:
                break;
            case ES512:
                break;
            case HS256:
                break;
            case HS384:
                break;
            case HS512:
                break;
            case PS256:
                break;
            case PS384:
                break;
            case PS512:
                break;
            case RS256:
                break;
            case RS384:
                break;
            case RS512:
                break;
            default:
                throw new SignatureException("Unsupported signature algorithm '" + jwtProperties.getSignType() + "'");
        }
    }
}
